/*
 * Copyright (c) 2018-2024 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "subcmd.h"

static int nodeReqCode[] =
{
    0, 0, 0, 0, 0, 0, 0,
    HOST_OPEN, HOST_CLOSE, HOST_REBOOT, HOST_SHUTDOWN,
    0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0,
    HOST_REBOOT, 0
};

static struct QuickPoolHostInfo *alloc_fake_hostinfo(unsigned int, char **);

/**
 * @brief       qp admin handler, to manage node in the cluster.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 * @param[in]   index         #3: index of the control: open, close, restart, shutdown,
 *                                suspend or resume.
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      2             #3: argument has syntax error
 * @retval      3             #4: failed to control the node in the cluster
 */
int
cmd_control_host(int argc, char *argv[], admin_control_t index)
{
    bool_t fOpt = FALSE;
    while (TRUE) {
        int option_index = 0;
        static struct option long_options[] = {
            {"force", required_argument, 0, 'f'},
            {"help", no_argument, 0, 'h'},
            {NULL, 0, 0, 0}
        };

        int opt;
        opt = getopt_long(argc, argv, "hf", long_options, &option_index);
        if (-1 == opt) {
            break;
        }

        switch (opt) {
        case 'f':
            fOpt = TRUE;
            break;
        case 'h':
            admin_usage(index);
            exit(0);
        case '?':
        default:
            admin_usage(index);
            exit(1);
        }
    }

    const char *strOption;
    switch (index) {
    case ADMIN_NODE_OPEN:
        strOption = "Open";
        break;
    case ADMIN_NODE_CLOSE:
        strOption = "Close";
        break;
    case ADMIN_JMRESTART:
        strOption = "Restart JM daemon on";
        break;
    case ADMIN_JM_RECONFIG:
        strOption = "Reconfig JM daemon on";
        break;
    case ADMIN_NODE_SHUTDOWN:
        strOption = "Shut down JM daemon on";
        break;
     default :
        fprintf(stderr, "Unknown control node subcmd specified.\n");

        admin_usage(index);
        exit(1);
    }

    int all = FALSE;
    unsigned int numNode;
    char **nodes=NULL,  **nodePoint=NULL;

    numNode = get_argument_names(argc, argv, optind, &nodes, &all, "hostC");

    /*reconfig must restart all g_masterlist's values services*/
    if (index == ADMIN_JM_RECONFIG && !all) {
        char *masterList = g_masterlist;
        unsigned int len = strlen(masterList);
        char *master = (char *)calloc(len+1, sizeof(char));
        if (NULL == master) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", len+1);
            exit(4);
        }
        char *token;

        numNode = 0;
        nodes = (char **)calloc(len+1, sizeof(char *));
        if (NULL == nodes) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", len+1);
            exit(4);
        }
        while (NULL != (token = get_string_token(&masterList, " ", master, len+1))) {
            nodes[numNode++] = copy_string(token);
            len = strlen(masterList);
        }

        if (0 < numNode) {
           nodePoint = nodes;
        }

        FREEUP(master);
    }else {
        if (0 == numNode || all) {
            nodePoint = NULL;
        } else {
            nodePoint = nodes;
        }
    }
      
    bool_t freeHostInfo = FALSE;
    struct QuickPoolHostInfo *hostInfo;
    struct QuickPoolHostReply *hostReply=NULL;
    if ((ADMIN_JM_RECONFIG == index || ADMIN_JMRESTART == index || ADMIN_NODE_SHUTDOWN == index)
        && !(0 == numNode && all)) {

        /* for jm restart/shutdown, if not specify a node name,
         *  let's do the operation for myself. */
        if ((hostInfo = alloc_fake_hostinfo(numNode, nodePoint)) == NULL) {
            FREEUP(nodes);
            exit(2);
        }

        FREEUP(nodes);
    } else {
        struct request nodeRequest;
        memset(&nodeRequest, 0, sizeof(struct request));

        nodeRequest.keyvalues = (struct keyvalue *)calloc(4, sizeof(struct keyvalue));
        if (NULL == nodeRequest.keyvalues) {
            fprintf(stderr, "calloc(%ld) failed due to %m.\n", 4*sizeof(struct keyvalue));
            exit(4);
        }

        int n=0;
        nodeRequest.keyvalues[n].key = REQUEST_KEY_OPTIONS;
        nodeRequest.keyvalues[n].value = (char *)calloc(12, sizeof(char));
        if (NULL == nodeRequest.keyvalues[n].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
        snprintf(nodeRequest.keyvalues[n].value, 12, "%d", 0);
        n ++;

        if (0 < numNode && NULL != nodePoint) {

            // Specified node names.
            nodeRequest.keyvalues[n].key = REQUEST_KEY_NUMBER;
            nodeRequest.keyvalues[n].value = (char *)calloc(12, sizeof(char));
            if (NULL == nodeRequest.keyvalues[n].value) {
                fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
                exit(4);
            }
            snprintf(nodeRequest.keyvalues[n].value, 12, "%d", numNode);
            n ++;

            nodeRequest.keyvalues[n].key = REQUEST_KEY_NAMES;
            unsigned int i, size = 0;
            for (i = 0; i < numNode; i++) {
                size += strlen(nodePoint[i]) + 1;
            }
            nodeRequest.keyvalues[n].value = (char *)calloc(size, sizeof(char));
            if (NULL == nodeRequest.keyvalues[n].value) {
                fprintf(stderr, "calloc(%d) failed due to %m.\n", size);
                exit(4);
            }

            for (i = 0; i < numNode; i++) {

                if (i == 0) {
                    strcpy(nodeRequest.keyvalues[n].value, nodePoint[i]);
                } else {
                    strcat(nodeRequest.keyvalues[n].value, " ");
                    strcat(nodeRequest.keyvalues[n].value, nodePoint[i]);
                }
            }
            n ++;
        } else {
            nodeRequest.keyvalues[n].key = REQUEST_KEY_NUMBER;
            nodeRequest.keyvalues[n].value = (char *)calloc(12, sizeof(char));
            if (NULL == nodeRequest.keyvalues[n].value) {
                fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
                exit(4);
            }
            snprintf(nodeRequest.keyvalues[n].value, 12, "%d", 0);
            n ++;

            nodeRequest.keyvalues[n].key = REQUEST_KEY_NAMES;
            nodeRequest.keyvalues[n].value = copy_string(qp_getmyhostname());
            n ++;
        }

        nodeRequest.number = n;

        hostReply = client_get_hostinfo(&nodeRequest, NULL);
        if (quickpool_errno != 0 || NULL == hostReply) {

            if (NULL == hostReply) {
                fprintf(stderr, "Fail to control host due to above error.\n");
            } else {
                fprintf(stderr, "%s\n", hostReply->error_message);
            }
            FREEUP(nodes);
            gf_xdr_free(gf_xdr_request, &nodeRequest);

            fflush(stderr);
            exit(3);
        }

        hostInfo = hostReply->hosts;
        numNode = hostReply->num_hosts;
        freeHostInfo = TRUE;
        FREEUP(nodes);
        gf_xdr_free(gf_xdr_request, &nodeRequest);
    }

    bool_t confirmFlag = FALSE;
    if (!fOpt && all && (index == ADMIN_JMRESTART || index == ADMIN_JM_RECONFIG || index == ADMIN_NODE_SHUTDOWN)) {
        /* --force is not specifed and the operation is on all the nodes,
         * let's ask for a confirmation. */

        char msg[PATH_MAX+1];
        snprintf(msg, PATH_MAX, "\n%s all the nodes? [y/n] ", strOption);
        confirmFlag = !askfor_confirm(msg);
    }

    struct request controlRequest;
    memset(&controlRequest, 0, sizeof(struct request));

    controlRequest.number = 2;
    controlRequest.keyvalues = (struct keyvalue *)calloc(2, sizeof(struct keyvalue));
    if (NULL == controlRequest.keyvalues) {
        fprintf(stderr, "calloc(%ld) failed due to %m.\n", 2*sizeof(struct keyvalue));
        exit(4);
    }

    controlRequest.keyvalues[0].key = REQUEST_KEY_REQUEST_CODE;
    controlRequest.keyvalues[0].value = (char *)calloc(12, sizeof(char));
    if (NULL == controlRequest.keyvalues[0].value) {
        fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
        exit(4);
    }
    snprintf(controlRequest.keyvalues[0].value, 12, "%d", nodeReqCode[index]);

    controlRequest.keyvalues[1].key = REQUEST_KEY_NAMES;

    int ret=0;
    unsigned int i;
    for (i = 0; i < numNode; i++) {
        // Trying to do operation on node one by one.

        if (strcmp(hostInfo[i].host, "lost_and_found") == 0
            && (HOST_REBOOT == nodeReqCode[index] || HOST_SHUTDOWN == nodeReqCode[index])) {

            if (!all) {
                fprintf(stderr, "<lost_and_found> is not a real host, ignored\n");
            }

            continue;
        }

        if (CLIENT == hostInfo[i].server) { // Do not do any operation on client node.
            continue;
        }

        char msg[PATH_MAX+1];
        snprintf(msg, PATH_MAX, "\n%s %s? [y/n] ", strOption, hostInfo[i].host);

        if (confirmFlag && !(askfor_confirm(msg))) {
            continue;
        }

        fprintf(stderr, "%s <%s> ...... ", strOption, hostInfo[i].host);
        fflush(stderr);

        FREEUP(controlRequest.keyvalues[1].value);
        controlRequest.keyvalues[1].value = copy_string(hostInfo[i].host);

        // call JS or JM to do the control operation.
        if (client_control_host(&controlRequest, NULL) < 0) {

            ret = 3;
            continue;
        }

        if (HOST_OPEN == nodeReqCode[index]) {
            if (hostInfo[i].status & (NODE_STATUS_BUSY | NODE_STATUS_WIND | NODE_STATUS_FULL)) {

                fprintf(stderr, "done : host remains closed due to ");

                if (hostInfo[i].status & NODE_STATUS_WIND) {
                    fprintf(stderr, "dispatch window; ");
                } else if (hostInfo[i].status & NODE_STATUS_FULL) {
                    fprintf(stderr, "job limit; ");
                } else if (hostInfo[i].status & NODE_STATUS_BUSY) {
                    fprintf(stderr, "load threshold; ");
                }

                fprintf(stderr, " \n");
                continue;
            }
        }

        fprintf(stderr, "done\n");
    }

    if (freeHostInfo) {
        gf_xdr_free(libxdr_host_reply, hostReply);
        FREEUP(hostReply);
    } else {
        FREEUP(hostInfo);
    }
    gf_xdr_free(gf_xdr_request, &controlRequest);

    exit(ret);
} // end function cmd_control_host

/* ------------------------------------------------
 *
 *  below are static functions used in this file
 *
 * ------------------------------------------------ */

/**
 * @brief       To create some fake host info.
 *
 * @param[in]   numhost       #1: number of host to be allocated
 * @param[in]   hosts         #2: string array of the host names
 *
 * @retval      NULL          #1: failed to alloc memory
 * @retval      not NULL      #2: input option is wrong or kind of input argument not specified
 *
 * @note        Do not free the content of QuickPoolHostInfo replied from this function.
 *
 */
static struct QuickPoolHostInfo *
alloc_fake_hostinfo(unsigned int numhost, char **hosts)
{
    struct QuickPoolHostInfo *hostInfo;
    hostInfo = (struct QuickPoolHostInfo *)calloc(numhost + 1, sizeof(struct QuickPoolHostInfo));
    if (NULL == hostInfo) {
        perror("calloc");
        return NULL;
    }

    if (NULL != hosts) {
        unsigned int i;

        for (i = 0; i < numhost; i++) {
            hostInfo[i].host = hosts[i];
        }
    } else {

        // client does not specify a node, specify myself.
        char *localHost = qp_getmyhostname();
        if (NULL == localHost) {
            hostInfo[0].host = (char *)"localhost";
        } else {
            hostInfo[0].host = localHost;
        }
    }

    return hostInfo;
} // end function alloc_fake_hostinfo
